Event System
Where You Can Use Event Handlers
- On
Graphics2D
elements to listen for user interactions:drag
,click
,zoom
,mousemove
, ...
- On
Graphics3D
elements:transform
- On any
EventObject
or its string equivalent - Many more — see Mouse and Keyboard
The event-driven approach is inspired by JavaScript and NodeJS, where one can subscribe to changes in any object. Here, it is expanded to utilize the full power of Wolfram Language's pattern-matching features.
See the reference section: Events
Thumb Rule
One event object — one handler function
ev = EventObject[]
EventHandler[ev, Print]
i.e.
To fire the event:
EventFire[ev, "Hello World!"];
To remove the handler from the event object, use:
Delete[ev]
Or the more universal:
EventRemove[ev]
This deletes the handler function but not the EventObject
.
To assign more event handlers, you need to clone the event object or use a different pattern on the same event object (see Pattern Matching).
String Equivalent
Since binding is based solely on the "Id"
field, one can omit the EventObject
head:
ev = "evid";
EventHandler[ev, Print]
Is equivalent to:
ev = EventObject[<|"Id" -> "evid"|>]
EventHandler[ev, Print]
Pattern Matching
An event can carry additional information using Wolfram Language patterns. This allows messages to be distributed across different handler functions based on the type of event fired. Using standard pattern-matching syntax with Rule
and RuleDelayed
, you can write more specific handler functions:
EventHandler["evid", {
"Topic" -> Function[data,
Echo["Topic::"];
Echo[data];
],
any_String :> Function[data,
Echo[StringJoin[any, "::"]];
Echo[data];
]
}];
To fire an event for a specific pattern, add an extra argument:
EventFire["evid", "Topic", "Hi!"];
EventFire["evid", "Whatever", "Hi!"];
Pattern matching is not limited to strings:
EventHandler["evid", {
_Abrakadabra -> Function[Null,
Echo["Got it!"];
],
_ -> Function[Null,
Echo["Wrong one"];
]
}];
EventFire["evid", Abrakadabra[], Null]
Note that the following are effectively the same:
EventHandler[ev, Print]
EventHandler[ev, {_ -> Print}]
And:
EventFire[ev, data]
EventFire[ev, "Default", data]
Cloning Events
If you want multiple handlers per pattern, clone the EventObject
or its string equivalent:
ev = EventObject[<|"Id" -> "evid"|>]
(* First handler *)
EventHandler[ev, Print];
(* Second handler *)
cloned = EventClone[ev];
EventHandler[cloned, Print];
This creates an event router subscribed to the original event object, which is then connected to multiple new event objects:
Operations on cloned
will not affect the original event:
Delete[cloned] or EventRemove[cloned]
Cloned objects inherit all properties (e.g., initial data) from the original object.
If you're sure two EventHandler
functions won't conflict, you can attach them to the same event object without cloning:
EventHandler[ev, {
"Pattern 1" -> func1
}];
EventHandler[ev, {
"Pattern 2" -> func2
}];
EventFire[ev, ..., data];
The patterns will be merged.
Return Value
Each handler function can return a value, allowing you to pass information back:
EventHandler[ev, Function[Null,
Now
]];
EventFire[ev, Null] // Echo
The Echo
will print the current date. This works with a chain of cloned events too:
EventHandler[ev, Function[Null, Now]];
EventHandler[EventClone[ev], Function[Null, Now]];
EventHandler[EventClone[ev], Function[Null, Now]];
EventFire[ev, Null] // Echo
The returned value will be a list of three nearly identical timestamps.
Merging Events
To update the state based on two independently occurring events:
ev1 = EventObject[<|"Id" -> "evid1"|>]
ev2 = EventObject[<|"Id" -> "evid2"|>]
joined = Join[ev1, ev2]
You don't need to clone events before joining — Join
does this automatically and preserves all other connections.
Properties
EventObject
can include additional keys. However, it is not a classical OOP object — handler functions can't access these properties. Only the "Id"
is globally stored.
Inheritable
The "Initial"
property specifies data to ship when the event is fired. When you apply Join
or EventClone
, the final initial values are merged:
ev1 = EventObject[<|"Id" -> "ev1", "Initial" -> <|"x" -> 1|>|>]
ev2 = EventObject[<|"Id" -> "ev1", "Initial" -> <|"y" -> 2|>|>]
Join[ev1, ev2]
Results in:
EventObject[<|"Id" -> "generatedId", "Initial" -> <|"x" -> 1, "y" -> 2|>|>]
If no data is passed to EventFire
, the "Initial"
value is used:
EventFire[ev]
Is equivalent to:
EventFire[ev, ev[[1]]["Initial"]]
Non-Inheritable
The "View"
property is useful in GUIs:
EventObject[<|"Id" -> "evid", "View" -> Graphics3D[Sphere[]]|>]
It renders the graphic when displayed, instead of showing the raw EventObject
.
Integration with Server/Client
The WLJSTransport framework lets JavaScript code trigger events. Use the global server
object:
server.kernel.io.fire('evid', 'message');
// or
server.kernel.io.fire('evid', 'message', 'pattern');
On the server side, handle it as usual:
EventHandler["evid", Print]